
import pjsua2 as pj
import time
import os

class SIPClient:
    def __init__(self, sip_server, sip_port, username, password, audio_file):
        # 配置参数
        self.sip_server = sip_server
        self.sip_port = sip_port
        self.username = username
        self.password = password
        self.audio_file = audio_file  # 播放的音频文件
        
        # 核心对象与状态
        self.endpoint = None         # SIP端点
        self.account = None          # 注册账号
        self.current_call = None     # 当前呼叫对象
        self.is_initialized = False  # 端点是否初始化完成
        self.is_registered = False   # 是否注册成功

    def init_endpoint(self):
        """初始化SIP端点，限制最大呼叫数为1（避免ID冲突）"""
        if self.is_initialized:
            print("✅ 端点已初始化，无需重复创建")
            return True
            
        try:
            # ensure_pj_thread_register("endpoint_init")
            
            # 创建端点并配置
            self.endpoint = pj.Endpoint()
            self.endpoint.libCreate()
            
            ep_cfg = pj.EpConfig()
            ep_cfg.logConfig.level = 3  # 日志级别：警告及以上
            ep_cfg.logConfig.consoleLevel = 3
            ep_cfg.uaConfig.maxCalls = 1  # 关键：限制同时只能有1个呼叫
            self.endpoint.libInit(ep_cfg)
            
            # 创建UDP传输
            transport_cfg = pj.TransportConfig()
            transport_cfg.port = 5062  # 本地端口（可修改为空闲端口）
            self.endpoint.transportCreate(pj.PJSIP_TRANSPORT_UDP, transport_cfg)
            
            # 启动端点
            self.endpoint.libStart()
            print("✅ SIP端点启动成功")
            
            # 初始化音频设备
            self.init_audio()
            self.is_initialized = True
            return True
            
        except Exception as e:
            print(f"❌ 端点初始化失败: {str(e)}")
            self.cleanup(force=True)
            return False

    def init_audio(self):
        """初始化音频设备"""
        # ensure_pj_thread_register("audio_init")
        try:
            aud_dev_mgr = self.endpoint.audDevManager()
            # 兼容新旧版本的音频配置
            try:
                aud_cfg = pj.AudDevManagerConfig()
                aud_dev_mgr.setConfig(aud_cfg)
            except AttributeError:
                print("⚠️ 使用兼容模式初始化音频设备")
            
            aud_dev_mgr.refreshDevs()
            print("✅ 音频设备初始化成功")
        except Exception as e:
            print(f"⚠️ 音频设备警告: {str(e)}（可能影响播放）")

    def register(self):
        """注册到SIP服务器"""
        if self.is_registered:
            print("✅ 已注册到服务器，无需重复注册")
            return True
        if not self.is_initialized:
            print("❌ 端点未初始化，无法注册")
            return False
            
        try:
            # ensure_pj_thread_register("register")
            
            # 配置账号信息
            acc_cfg = pj.AccountConfig()
            acc_cfg.idUri = f"sip:{self.username}@{self.sip_server}"
            
            # 认证信息
            cred = pj.AuthCredInfo("digest", "*", self.username, 0, self.password)
            acc_cfg.sipConfig.authCreds.append(cred)
            
            # 注册服务器地址
            acc_cfg.regConfig.registrarUri = f"sip:{self.sip_server}:{self.sip_port}"
            
            # 创建账号并注册
            self.account = SIPAccount(self)
            self.account.create(acc_cfg)
            print(f"🔍 正在注册到 {self.sip_server}:{self.sip_port}...")
            return True
            
        except Exception as e:
            print(f"❌ 注册失败: {str(e)}")
            self.account = None
            return False

    def make_call(self,target):
        """发起新呼叫（确保旧呼叫已彻底销毁）"""

        print("++++++++++++++++++++++++++++++++++++++++++++==")
        if not self.is_registered or not self.account:
            print("❌ 未注册或账号无效，无法呼叫")
            return False
            
        try:
            # ensure_pj_thread_register("make_call")
            
            # 关键：彻底销毁旧呼叫（释放call_id）
            if self.current_call:
                print("🗑️ 清理历史呼叫资源...")
                try:
                    self.current_call.destroy()  # 销毁底层资源
                except Exception as e:
                    print(f"⚠️ 清理旧呼叫失败: {str(e)}")
                self.current_call = None  # 置空
            
            # 创建新呼叫（使用自动分配的call_id）
            self.current_call = SIPCall(
                self.account, 
                pj.PJSUA_INVALID_ID,  # 让库自动分配有效ID
                self.audio_file
            )
            
            # 发起呼叫
            call_uri = f"sip:{target}@{self.sip_server}"
            call_prm = pj.CallOpParam()
            self.current_call.makeCall(call_uri, call_prm)
            print(f"📞 正在呼叫 {target}...")
            return True
            
        except Exception as e:
            print(f"❌ 呼叫失败: {str(e)}")
            self.current_call = None

            return False

    def wait_call_complete(self, timeout=60):
        """等待当前呼叫结束"""
        start_time = time.time()
        while time.time() - start_time < timeout:
            if not self.current_call:
                return True  # 呼❌ 呼叫回调异常: 'CallInfo' object has no attribute 'callId'叫已销毁
            # 检查呼叫状态
            try:
                call_info = self.current_call.getInfo()
                if call_info.state == pj.PJSIP_INV_STATE_DISCONNECTED:
                    return True  # 呼叫已断开
            except:
                return True  # 呼叫对象已失效
            time.sleep(1)
        print(f"⏰ 呼叫超时（{timeout}秒）")
        return False

    def start_call_sequence(self,target):
        """单次呼叫完整流程：初始化→注册→呼叫→等待结束"""
        if not self.init_endpoint():
            return False
        if not self.register():
            return False
        # 等待注册完成（最多10秒）
        wait_time = 0
        while not self.is_registered and wait_time < 10:
            time.sleep(1)
            wait_time += 1
        if not self.is_registered:
            print("❌ 注册超时，无法呼叫")
            return False
        # 发起呼叫并等待结束
        if self.make_call(target):
            self.wait_call_complete()
            time.sleep(1)  # 等待资源释放
        return True

    def cleanup(self, force=False):
        """清理资源（force=True时释放所有资源）"""
        # ensure_pj_thread_register("cleanup")
        print("\n开始清理资源...")
        
        # 销毁当前呼叫
        if self.current_call:
            try:
                self.current_call.destroy()
            except:
                pass
            self.current_call = None
        
        # 强制清理时释放账号和端点
        if force:
            if self.account:
                try:
                    self.account.destroy()
                except:
                    pass
                self.account = None
            if self.endpoint:
                try:
                    self.endpoint.libDestroy()
                except:
                    pass
                self.endpoint = None
            self.is_initialized = False
            self.is_registered = False
        
        print("✅ 资源清理完成")

# ------------------------------
# SIP账号回调类（处理注册状态）
# ------------------------------
class SIPAccount(pj.Account):
    def __init__(self, client):
        # ensure_pj_thread_register("sip_account_init")
        pj.Account.__init__(self)
        self.client = client  # 关联主客户端

    # @pj_thread_register
    def onRegState(self, prm):
        """注册状态变化回调"""
        # ensure_pj_thread_register("on_reg_state")
        try:
            acc_info = self.getInfo()
            print(f"\n[注册状态] 状态码: {acc_info.regStatus} ({acc_info.regStatusText})")
            
            if acc_info.regStatus == 200:
                print("✅ 注册成功！")
                self.client.is_registered = True
            elif acc_info.regStatus != 100:  # 排除临时状态（100 Trying）
                print(f"❌ 注册失败: {acc_info.regStatusText}")
                self.client.is_registered = False
        except Exception as e:
            print(f"❌ 注册回调异常: {str(e)}")

# ------------------------------
# SIP呼叫回调类（处理呼叫状态和音频）
# ------------------------------
class SIPCall(pj.Call):
    def __init__(self, account, call_id=pj.PJSUA_INVALID_ID, audio_file=None):
        # ensure_pj_thread_register("sip_call_init")
        super().__init__(account, call_id)
        self.audio_file = audio_file
        self.audio_player = None  # 音频播放器
        self.is_active = False    # 呼叫是否活跃

    # @pj_thread_register
    def onCallState(self, prm):
        """呼叫状态变化回调"""
        # ensure_pj_thread_register("on_call_state")
        try:
            call_info = self.getInfo()
            # print(f"[呼叫状态] {call_info.stateText}（ID: {call_info.callId}）")
            
            # 呼叫接通：播放音频
            if call_info.state == pj.PJSIP_INV_STATE_CONFIRMED:
                print("📞 呼叫已接通，开始播放音频...")
                self.is_active = True
                self.play_audio()
            
            # 呼叫结束：清理资源
            elif call_info.state == pj.PJSIP_INV_STATE_DISCONNECTED:
                print("📞 呼叫已结束")
                self.is_active = False
                self.stop_audio()
                # 主动销毁自身，释放call_id
                try:
                    self.destroy()
                except Exception as e:
                    print(f"⚠️ 销毁呼叫失败: {str(e)}")
        except Exception as e:
            print(f"❌ 呼叫回调异常: {str(e)}")

    def play_audio(self):
        """播放指定音频文件"""
        # ensure_pj_thread_register("play_audio")
        if not self.audio_file or not os.path.exists(self.audio_file):
            print(f"❌ 音频文件不存在: {self.audio_file}")
            return
            
        try:
            # 创建音频播放器
            self.audio_player = pj.AudioMediaPlayer()
            self.audio_player.createPlayer(self.audio_file)
            print(f"▶️ 加载音频文件: {self.audio_file}")
            
            # 获取通话音频通道并播放
            call_audio = self.getAudioMedia(-1)
            if call_audio:
                self.audio_player.startTransmit(call_audio)
                print("🔊 音频开始向对方播放")
            else:
                print("❌ 无法获取通话音频通道")
                self.stop_audio()
                
        except Exception as e:
            print(f"❌ 音频播放失败: {str(e)}")
            self.stop_audio()

    def stop_audio(self):
        """停止音频播放并清理"""
        # ensure_pj_thread_register("stop_audio")
        if self.audio_player:
            try:
                call_audio = self.getAudioMedia(-1)
                if call_audio:
                    self.audio_player.stopTransmit(call_audio)
                self.audio_player.destroy()
                print("⏹️ 音频播放已停止")
            except Exception as e:
                print(f"⚠️ 音频清理失败: {str(e)}")
            self.audio_player = None